#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "ao_test.h"
#include "pcie_mem.h"

#define AO_PLUSE_DEFAULT_CYCLE      (1000)      /* 默认脉冲周期，单位：微秒 */
#define AO_GP8311_RSET              (1.0)      /* GP8311 的 Rset 电阻值，单位：欧姆 */

enum ao_bar0_offset_t {
    AO_PULSE_WIDTH_BAR0_OFFSET = 0x3C,      /* 设置 AO 脉冲宽度寄存器偏移 */
    AO_SET_ID_BAR0_OFFSET = 0x50,           /* 设置 AO ID 寄存器偏移 */
};

enum ao_pulse_shift_t {
    AO_PULSE_WITH_SHIFT = 16,               /* AO 脉冲宽度左移位数 */
    AO_PULSE_CYCLE_SHIFT = 0,               /* AO 脉冲周期左移位数 */
};

struct ao_info_t {
    uint8_t channel;                        /* 通道号 */
    uint8_t channel_min;                    /* 最小通道号 */
    uint8_t channel_max;                    /* 最大通道号 */
    uint32_t value;                         /* AO 输出值 */
    uint32_t value_min;                     /* 最小 AO 输出值 */
    uint32_t value_max;                     /* 最大 AO 输出值 */
    uint16_t pulse_width;                   /* AO 脉冲宽度 */
    uint16_t pulse_cycle;                   /* AO 脉冲周期 */
};

static void set_all_ao(struct pcie_mem_ctx *ctx, uint8_t channel, uint32_t value)
{
    volatile uint32_t *addr;

    if (!ctx->map_base) {
        printf("错误: 内存未映射\n");
        return;
    }

    addr = (volatile uint32_t *)((uintptr_t)ctx->map_base);
    *(volatile uint32_t *)((uintptr_t)addr + AO_SET_ID_BAR0_OFFSET) = channel;
    *(volatile uint32_t *)((uintptr_t)addr + AO_PULSE_WIDTH_BAR0_OFFSET) = value;

    printf("写入: reg=0x%04X, val=0x%08X\n",
           AO_SET_ID_BAR0_OFFSET, channel);
    printf("写入: reg=0x%04X, val=0x%08X\n",
           AO_PULSE_WIDTH_BAR0_OFFSET, value);
}

void set_all_ao_test(struct pcie_mem_ctx *ctx, struct ao_info_t ao_info)
{
    uint32_t reg_val = 0;

    /* GP8311 Iout=Duty*5V*10/Rset (Rset=1K) */
    ao_info.pulse_width = ao_info.pulse_cycle * ((ao_info.value * AO_GP8311_RSET) / (5.0 * 10.0));
    reg_val = (ao_info.pulse_width << AO_PULSE_WITH_SHIFT) | 
              (ao_info.pulse_cycle << AO_PULSE_CYCLE_SHIFT);
    if ((ao_info.channel >= ao_info.channel_min) && (ao_info.channel <= ao_info.channel_max)) {
        set_all_ao(ctx, ao_info.channel, reg_val);
        printf("设置通道 %d 的 AO 输出值为 %dmA\n", ao_info.channel, ao_info.value);
    } else {
        set_all_ao(ctx, ao_info.channel, reg_val);
        printf("设置全部 AO 输出值为 %dmA\n", ao_info.value);
    }
}

static int usage(int argc, char *argv[])
{
    if ((argc < 3) || (argc > 4)) {
        printf("用法: %s %s channel value(mA)\n", argv[0], argv[1]);
        printf("示例: %s %s 0 10\n", argv[0], argv[1]);
        printf("用法: %s %s value(mA)\n", argv[0], argv[1]);
        printf("示例: %s %s 10\n", argv[0], argv[1]);
        return EXIT_FAILURE;
    } else {
        return EXIT_SUCCESS;
    }
}

int ao_test(int argc, char *argv[])
{
    int ret = 0;
    const char *device_address = PCIE_AO_DEVICE_ADDRESS;
    uint32_t base_addr = PCIE_AO_BASE_ADDR_BAR0;
    size_t map_size = PCIE_AO_BAR0_MAP_SIZE;
    struct pcie_mem_ctx ctx = {
        .fd = -1,
        .map_base = NULL,
        .phys_base = 0,
        .map_size = 0
    };
    struct ao_info_t ao_info = {
        .channel = 0,
        .channel_min = 0,
        .channel_max = 11,
        .value = 10,
        .value_min = 0,
        .value_max = 25,
        .pulse_cycle = AO_PLUSE_DEFAULT_CYCLE,
    };

    if (EXIT_SUCCESS != usage(argc, argv)) {
        return EXIT_FAILURE;
    }

    if (argc == 4) {
        ao_info.channel = atoi(argv[2]);
        ao_info.value = atoi(argv[3]);
    } else if (argc == 3) {
        ao_info.value = atoi(argv[2]);
        ao_info.channel = ao_info.channel_max + 1;
    }

    if (ao_info.value < ao_info.value_min || 
        ao_info.value > ao_info.value_max) {
        printf("参数错误: %dmA ≤ value=%d ≤ %dmA\n", 
                ao_info.value_min, ao_info.value, ao_info.value_max);
        return EXIT_FAILURE;
    }

    printf("正在设置通道 %d 的电平至 0x%04X...\n",
            ao_info.channel, ao_info.value);

    if (true == is_pcie_device_enabled(device_address)) {
        printf("设备已启用\n");
    } else {
        printf("设备未启用\n");
        if (enable_pcie_device(device_address) == 0) {
            printf("设备启用成功\n");
        } else {
            printf("设备启用失败!\n");
            return EXIT_FAILURE;
        }
    }

    ret = init_pcie_mem(&ctx, base_addr, map_size);
    if (ret < 0) {
        printf("初始化内存映射失败!\n");
        return EXIT_FAILURE;
    }

    set_all_ao_test(&ctx, ao_info);

    cleanup_pcie_mem(&ctx);

    printf("通道 %d 的测试已完成\n", ao_info.channel);

    return EXIT_SUCCESS;
}
